<?php
// +----------------------------------------------------------------------
// | Author: July
// +----------------------------------------------------------------------
// | 创建时间: 2023-12-27 20:39:42
// +----------------------------------------------------------------------
// | 包入口
// +----------------------------------------------------------------------

namespace july\julyadmin;

use Closure;
use think\App;
use think\helper\Str;
use think\Request;
use think\Response;

class AppRun
{
    /** @var App */
    protected $app;

    /**
     * 请求对象
     * @var Request
     */
    protected $request;

    /**
     * 应用名称
     * @var string
     */
    protected $name;

    /**
     * 应用名称
     * @var string
     */
    protected $appName;

    /**
     * 应用路径
     * @var string
     */
    protected $path;

    private $view_config = [
        // 模板引擎类型使用Think
        'type'          => 'Think',
        // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法
        'auto_rule'     => 1,
        // 模板目录名
        'view_dir_name' => 'view',
        // 模板后缀
        'view_suffix'   => 'html',
        // 模板文件名分隔符
        'view_depr'     => DIRECTORY_SEPARATOR,
        'tpl_begin'     => '{',
        // 模板引擎普通标签结束标记
        'tpl_end'       => '}',
        // 标签库标签开始标记
        'taglib_begin'  => '{',
        // 标签库标签结束标记
        'taglib_end'    => '}',
    ];

    public function __construct(App $app)
    {
        $this->app = $app;
        //载入框架公共函数文件
        include realpath(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'common.php';
        $this->app->http->setRoutePath($this->getRoutePath());
    }

    /**
     * 多模块解析
     * @access public
     * @param Request $request
     * @param Closure $next
     * @return Response
     */
    public function handle($request, Closure $next)
    {
        //注册自定义标签库
        $this->registerTagLibs();

        $this->name    = $this->app->http->getName();
        $this->path    = $this->app->http->getPath();
        $this->request = $this->app->request;
        if (!$this->parseModule()) {
            return $next($request);
        }

        return $this->app->middleware->pipeline('julyadmin')
            ->send($request)
            ->then(function ($request) use ($next) {
                return $next($request);
            });
    }

    /**
     * 注册自定义标签
     * @Author   July
     * @DateTime 2023-12-20T10:08:40+0800
     */
    private function registerTagLibs()
    {

        $dir  = __DIR__ . DIRECTORY_SEPARATOR . 'taglib';
        $dir  = str_ireplace("\\", "/", $dir);
        $tags = scandir($dir);
        // 除去无用文件
        foreach ($tags as $key => $block) {
            if ($block == '.' || $block == '..') {
                unset($tags[$key]);
            } else {
                $tags[$key] = 'july\\julyadmin\\taglib\\' . str_replace('.php', '', $tags[$key]);
            }
        }

        //注册自定义标签
        $config                    = config('view');
        $config['taglib_pre_load'] = implode(',', $tags);
        $this->app->config->set($config, 'view');
    }

    /**
     * 解析模块
     * @Author   July
     * @DateTime 2023-12-28T21:25:40+0800
     */
    protected function parseModule()
    {
        $inletName   = $this->getInletName();
        $module_bind = '';
        if ($this->name || ($inletName && !in_array($inletName, ['index', 'think']))) {
        } else {
            // 自动多应用识别
            $this->app->http->setBind(false);

            $module_bind = $this->app->config->get('app.domain_bind', []);
            if (!empty($module_bind)) {
                // 获取当前子域名
                $subDomain = $this->app->request->subDomain();
                $domain    = $this->app->request->host(true);
                if (isset($module_bind[$domain])) {
                    $bind = $module_bind[$domain];
                } elseif (isset($module_bind[$subDomain])) {
                    $bind = $module_bind[$subDomain];
                } elseif (isset($module_bind['*'])) {
                    $bind = $module_bind['*'];
                } else {
                    $bind = $this->app->route->getDomainBind('-');
                }
            } else {
                $bind = $this->app->route->getDomainBind('-');
            }
        }

        $url = str_replace(config('route.pathinfo_depr'), '|', $this->path());

        //加载我的开发包下面对应的文件和url，如果没有返回false
        $res = $this->cherckModule($url, $bind);
        return false;
    }

    /**
     * 验证路由
     * @Author   July
     * @DateTime 2024-01-14T09:02:17+0800
     * @param string $url   当前url
     * @param string $bind  域名绑定信息
     * @return   boolean    是否找到合适的路由true，false
     */
    private function cherckModule($url, $bind)
    {
        if ($bind) {
            $url = $url ? $bind . '|' . $url : $bind;
        }

        $result = explode('|', $url);

        $module         = strip_tags(isset($result[0]) && !empty($result[0]) ? strtolower($result[0]) : config('app.default_app'));
        $app_map_module = $module;
        $controller     = strip_tags(isset($result[1]) && !empty($result[1]) ? $result[1] : config('route.default_controller'));
        $action         = strip_tags(isset($result[2]) && !empty($result[2]) ? $result[2] : config('route.default_action'));
        //这里处理应用映射
        $app_map = config('app.app_map', []);
        if ($app_map) {
            if ($key = array_search($module, $app_map)) {
                $app_map_module = $key;
            }
        }

        $controll_dir    = app_path() . $module . DIRECTORY_SEPARATOR . config('route.controller_layer') . DIRECTORY_SEPARATOR;
        $controller_file = $controll_dir . ucfirst($controller) . ".php";
        $namespace       = "app\\{$module}\\" . config('route.controller_layer');
        $class           = "app\\{$module}\\" . config('route.controller_layer') . "\\" . ucfirst($controller);
        $class_exists    = class_exists($class);

        if (!file_exists($controller_file) || !$class_exists || !method_exists($class, $action)) {
            $appdir = app_path();
            $preg   = "/([a-zA-Z\d]+)/i";
            preg_match_all($preg, $appdir, $res);
            $appdir_name = $res[1][count($res[1]) - 1];
            $class       = $appdir_name . '\\' . $module . '\\' . config('route.controller_layer') . '\\' . ucfirst($controller);
            //判断如果当前url访问的路由不存在的话就调用我自己的
            $class_exists           = class_exists($class);
            $method_function_exists = false;
            if ($class_exists) {
                $method_function_exists = method_exists($class, $action);
            }

            if (!$class_exists || !$method_function_exists) {

                $namespace    = "july\\julyadmin";
                $class        = "july\\julyadmin\\" . $module . "\\" . config('route.controller_layer') . "\\" . ucfirst($controller);
                $class_exists = class_exists($class);
                if ($class_exists && method_exists($class, $action)) {

                    $path = $this->app->request->pathinfo();

                    $this->app->setAppPath(__DIR__ . DIRECTORY_SEPARATOR . $module);
                    $this->app->http->path(__DIR__ . DIRECTORY_SEPARATOR . $module);

                    //设置命名空间
                    $this->app->config->set(['app_namespace' => $namespace . '\\' . strtolower($module)], 'app');
                    $this->app->http->name('');
                    $this->app->request->setRoot('/' . $app_map_module);
                    $this->request->setController(Str::studly($controller))->setAction($action);
                    $this->app->request->setPathinfo($app_map_module . '/' . (strpos($path, '/') ? ltrim(strstr($path, '/'), '/') : ''));

                    //设置view配置
                    $this->app->config->set($this->view_config, 'view');

                    //设置模板目录
                    $template = __DIR__ . DIRECTORY_SEPARATOR . strtolower($module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR);
                    $template = str_replace("/", DIRECTORY_SEPARATOR, $template);
                    $template = str_replace("\\", DIRECTORY_SEPARATOR, $template);
                    \think\facade\View::config(['view_path' => $template]);
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 递归获取extend目录下的所有包含命名空间的php文件
     * @Author   July
     * @DateTime 2024-01-14T08:55:18+0800
     * @param string                   $extend_path extend目录
     * @param array  $routers      所有命名空间对应文件名的键值对数组
     * @return   array                                $routers
     * $routers[路由命名空间] = [
    'filename' => '路由指向的文件具体路径'
    ];
     */
    private function getExtendClass($extend_path = '', &$routers = [])
    {
        if (!$extend_path) {
            $extend_path = $this->app->getRootPath() . 'extend';
        }
        $handle = opendir($extend_path);
        while ($file = readdir($handle)) {
            if ($file == '.' || $file == "..") {
                continue;
            }

            $temp = explode('.', $file);
            if (!is_dir($extend_path . DIRECTORY_SEPARATOR . $file) && count($temp) > 1 && $temp[count($temp) - 1] == 'php' && stripos($extend_path . "/" . $file, DIRECTORY_SEPARATOR . config('route.controller_layer')) !== false) {
                //读取控制器代码
                $code = file_get_contents($extend_path . "/" . $file);
                $code = removeExplanatoryNote($code);
                //找到命名空间
                $preg = '~namespace ([a-zA-Z\\\]+)~';
                preg_match_all($preg, $code, $res);
                if (isset($res[1][0]) && $res[1][0]) {
                    $preg = '~namespace ([a-zA-Z\\\]+)\\\\' . config('route.controller_layer') . '~';
                    preg_match_all($preg, $code, $app_namespace_res);
                    $routers[$res[1][0] . '\\' . $temp[0]] = [
                        'app_namespace' => $app_namespace_res[1][0],
                        'namespace'     => str_replace('\\', '\\\\', $res[1][0]),
                        'filename'      => $extend_path . DIRECTORY_SEPARATOR . $file,
                    ];
                    if (isset($app_namespace_res[1][0]) && $app_namespace_res[1][0]) {
                        include_once $extend_path . DIRECTORY_SEPARATOR . $file;
                    }
                }
            }
            if (is_dir($extend_path . DIRECTORY_SEPARATOR . $file)) {
                $this->getExtendClass($extend_path . DIRECTORY_SEPARATOR . $file, $routers);
            }
        }
        closedir($handle);

        return $routers;
    }

    /**
     * 递归导入extend目录下的所有包含命名空间的模型文件
     * @Author   July
     * @DateTime 2024-01-14T09:00:03+0800
     * @param string                   $extend_path extend目录
     * @param array  $models           所有模型文件路径
     * @return   array                                $models
     * $models[] = '模块具体文件名和路径';
     */
    private function getExtendModels($extend_path = '', &$models = [])
    {
        if (!$extend_path) {
            $extend_path = $this->app->getRootPath() . 'extend';
        }

        $handle = opendir($extend_path);
        while ($file = readdir($handle)) {
            if ($file == '.' || $file == "..") {
                continue;
            }

            $temp = explode('.', $file);
            if (!is_dir($extend_path . DIRECTORY_SEPARATOR . $file) && count($temp) > 1 && $temp[count($temp) - 1] == 'php' && stripos($extend_path . "/" . $file, DIRECTORY_SEPARATOR . 'model') !== false) {
                //读取模型代码
                $code = file_get_contents($extend_path . "/" . $file);
                $code = removeExplanatoryNote($code);
                //找到命名空间
                $preg = '~namespace ([a-zA-Z\\\]+)~';
                preg_match_all($preg, $code, $res);

                if (isset($res[1][0]) && $res[1][0]) {
                    $models[] = $extend_path . DIRECTORY_SEPARATOR . $file;
                }
            }
            if (is_dir($extend_path . DIRECTORY_SEPARATOR . $file)) {
                $this->getExtendModels($extend_path . DIRECTORY_SEPARATOR . $file, $models);
            }
        }
        closedir($handle);

        return $models;
    }

    /**
     * 获取当前请求URL的pathinfo信息(不含URL后缀)
     * @Author   July
     * @DateTime 2024-01-14T08:54:30+0800
     * @return   string                   返回当前url的pathinfo字符串
     */
    protected function path(): string
    {
        $suffix   = config('route.url_html_suffix');
        $pathinfo = $this->request->pathinfo();
        if ($pathinfo == '404.' . $suffix) {
            $pathinfo = $_SERVER['REQUEST_URI'] ?? '';
        }
        if (false === $suffix) {
            // 禁止伪静态访问
            $path = $pathinfo;
        } elseif ($suffix) {
            // 去除正常的URL后缀
            $path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
        } else {
            // 允许任何后缀访问
            $path = preg_replace('/\.' . $this->request->ext() . '$/i', '', $pathinfo);
        }
        $app_map = config('app.app_map', []);
        if ($app_map) {
            //如果当前模块名在被映射的模块内则不允许访问，必须用映射名访问
            foreach ($app_map as $map_k => $map) {
                if (strtolower($map) == strtolower($path)) {
                    return '';
                }
            }
            //判断当前模块有没有被映射，如果有被映射返回映射名
            foreach ($app_map as $map_k => $map) {
                if (strtolower($map_k) == strtolower($path)) {
                    return $map;
                }
            }

            $temp = explode('/', $path);
            if (count($temp) > 0) {
                //如果当前模块名在被映射的模块内则不允许访问，必须用映射名访问
                foreach ($app_map as $map_k => $map) {
                    if (strtolower($map) == strtolower($temp[0])) {
                        return '';
                    }
                }

                //判断当前模块有没有被映射，如果有被映射返回映射名
                foreach ($app_map as $map_k => $map) {
                    if (strtolower($map_k) == strtolower($temp[0])) {
                        $temp[0] = $map;
                    }
                }
                $path = implode('/', $temp);
            }
        }
        return $path;
    }

    /**
     * 获取当前运行的入口名称
     * @Author   July
     * @DateTime 2024-01-14T08:53:47+0800
     * @return   string                   返回入口名称，框架默认是index.php，所以返回的是index
     */
    protected function getInletName(): string
    {
        if (isset($_SERVER['SCRIPT_FILENAME'])) {
            $file = $_SERVER['SCRIPT_FILENAME'];
        } elseif (isset($_SERVER['argv'][0])) {
            $file = realpath($_SERVER['argv'][0]);
        }

        return isset($file) ? pathinfo($file, PATHINFO_FILENAME) : '';
    }

    /**
     * 获取路由目录
     * @access protected
     * @return string
     */
    protected function getRoutePath(): string
    {
        return $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR;
    }
}
